﻿#include "common.h"
#include "context.h"
#include "opencv2/opencv.hpp"
#include "audio/mixer.h"
#include "audio/ext.h"
#include "texture.h"
#include "shader.h"
#include "target.h"

using namespace std;
using namespace anip;

static const char* vertex_shader_text =
"attribute vec2 vPos;"
"in vec2 pos;"
"void main()"
"{"
"    gl_Position = vec4(vPos, 0.0, 1.0);"
"    pos = vPos;"
"}";

void usage(char *myname)
{
	cout << "Usage: " << myname << " file [w h [-f]] <input-groups>" << endl
		 << "       This opens the shader file (defaults to \"shader.glsl\")" << endl
		 << "       and plays it in real-time. If the option -f is given," << endl
		 << "       fullscreen will be used." << endl;
	cout << "Usage: " << myname << " file w h -out time out_file <input-groups>" << endl
		 << "       This opens the shader file (defaults to \"shader.glsl\")" << endl
	 	 << "       and renders it to an MP4 file of given length, at 24" << endl
	 	 << "       frames per second. No graphic window will be shown." << endl
		 << "       NOTE: the output resolution cannot be larger than the" << endl
		 << "       screen's." << endl;
	cout << "Usage: " << myname << " -h" << endl
		 << "       This shows the help message." << endl << endl;
	cout << "About <input-groups>:" << endl
		 << "       -ii uniform_name i file    for image files" << endl
		 << "       -iv uniform_name i file    for video files" << endl
		 << "       -ic uniform_name i index   for camera (video capture) input" << endl
		 << "       -ia uniform_name file      for audio files; currently" << endl 
		 << "                                  only OGG files are supported!" << endl;

	exit(1);
}

struct InputImage
{
	string name;
	int i;
	Texture2D* tex;
};

struct InputVideo
{
	string name;
	int i;
	int pos = 0;
	float framerate;
	Texture2D* tex;
	cv::VideoCapture* vid;
};

struct InputAudio
{
	string name;
	anip::audio::Waveform* wav;
};

void setUniforms(anip::Shader& shader, double t, 
	std::vector<InputAudio*>& audios, 
	std::vector<InputVideo*>& videos)
{
	static cv::Mat frame, flipped;

	shader.set("time", (float)t);
	for (InputAudio* audio : audios)
	{
		shader.set(audio->name, (float)audio->wav->getAmplitude(t));
	}
	for (InputVideo* video : videos)
	{
		while (video->pos / video->framerate < t)
		{
			*video->vid >> frame;
			video->pos++;
		}
		if (!frame.empty())
		{
			cv::flip(frame, flipped, 0);
			video->tex->setPartialData(
				0, 0, video->tex->width(), video->tex->height(), flipped.ptr());
		}
	}
}

using namespace anip::audio;

int main(int argc, char **argv)
{
	//argv = new char*[5]{argv[0], "pulse.glsl", "-ia", "music", "bts.ogg"};
	//argc = 5;

	char* path = "shader.glsl";
	int width = 640;
	int height = 480;
	bool fullscreen = false;

	bool write = false;
	double length = 0;
	cv::VideoWriter* writer = nullptr;

	vector<InputImage*> images;
	vector<InputVideo*> videos;
	vector<InputAudio*> audios;

	int argpos = 1;

	if (argc >= 2)
	{
		if (strncmp(argv[1], "-h", 2) == 0)
			usage(argv[0]);
		path = argv[1];
		argpos = 2;
	}
	if (argc >= 4 && argv[2][0] != '-')
	{
		if (sscanf(argv[2], "%d", &width) +
			sscanf(argv[3], "%d", &height) != 2) usage(argv[0]);
		argpos = 4;
	}

	if (argc >= 5 && strcmp(argv[4], "-f") == 0)
	{
		fullscreen = true;
		argpos = 5;
	}
	if (argc >= 7 && strcmp(argv[4], "-out") == 0)
	{
		if (sscanf(argv[5], "%lf", &length) != 1) usage(argv[0]);
		writer = new cv::VideoWriter(argv[6], 
			cv::VideoWriter::fourcc('D', 'I', 'V', 'X'),
			24, cv::Size(width, height));
		if (!writer->isOpened())
		{
			cerr << "Cannot open video writer" << endl;
			exit(1);
		}
		write = true;
		argpos = 7;
	}
	//if (argpos == 1 && argc != 1) usage(argv[0]);

	while (argpos < argc)
	{
		if (strcmp(argv[argpos], "-ii") == 0)
		{
			if (argc - argpos != 4) usage(argv[0]);
			InputImage* in = new InputImage;
			in->name = argv[argpos + 1];
			sscanf_s(argv[argpos + 2], "%d", &in->i);
			try
			{
				in->tex = Texture2D::createFromFile(argv[argpos + 3]);
			}
			catch (exception& e)
			{
				cout << "4";
				cerr << "In input group '"<< argv[argpos + 1] << "': " << e.what() << endl;
				exit(1);
			}
			images.push_back(in);
			argpos += 4;
		}
		else if (strcmp(argv[argpos], "-iv") == 0)
		{
			if (argc - argpos != 4) usage(argv[0]);
			InputVideo* in = new InputVideo;
			in->name = argv[argpos + 1];
			sscanf_s(argv[argpos + 2], "%d", &in->i);
			in->vid = new cv::VideoCapture(argv[argpos + 3]);
			if (!in->vid->isOpened())
			{
				cerr << "Error opening video in '" << in->name << "'" << endl;
				exit(1);
			}

			int w = in->vid->get(cv::CAP_PROP_FRAME_WIDTH);
			int h = in->vid->get(cv::CAP_PROP_FRAME_HEIGHT);
			in->framerate = in->vid->get(cv::CAP_PROP_FPS);
			TextureOptions opt;
			opt.format = ColorFormat::RGB;
			in->tex = new Texture2D(w, h, &opt);

			videos.push_back(in);
			argpos += 4;
		}
		else if (strcmp(argv[argpos], "-ia") == 0)
		{
			if (argc - argpos != 3) usage(argv[0]);
			InputAudio* in = new InputAudio;
			in->name = argv[argpos + 1];
			cout << "Reading audio file for '" << in->name << "'" << endl;
			in->wav = Waveform::fromOGG(argv[argpos + 2]);
			audios.push_back(in);
			argpos += 3;
		}
		else
		{
			cerr << "Invalid option: " << argv[argpos] << endl;
			exit(1);
		}
	}

	if (!anip::fs::fileExists(path))
	{
		cerr << "Input file does not exist: " << path << endl;
		exit(1);
	}

	anip::init();
	Context::init({ (float)width, (float)height });

	try
	{
		string file = anip::readFile(path);
		anip::ShaderPart vert(ShaderPart::Type::Vertex, vertex_shader_text);
		anip::ShaderPart frag(ShaderPart::Type::Fragment, file);
		anip::Shader shader{ &vert, &frag };
		anip::Texture2D lastFrame(width, height);

		shader.use();
		shader.set("resolution", (float)width, (float)height);
		shader.set("prevFrame", lastFrame, 0);
		for (InputImage* i : images)
			shader.set(i->name, *i->tex, i->i);
		for (InputVideo* i : videos)
			shader.set(i->name, *i->tex, i->i);

		if (write)
		{
			cout << "Writing started" << endl;
			unsigned char* data = new unsigned char[width * height * 4];
			double t = 0;
			int frame = 0;

			cv::Mat orig(height, width, CV_8UC4, data), 
				rgb(height, width, CV_8UC3, cv::Scalar(0, 0, 0)),
				flipped(height, width, CV_8UC3, cv::Scalar(0, 0, 0));
			
			while (t < length)
			{
				setUniforms(shader, t, audios, videos);

				Context::fill();
				Context::defaultTarget().readDataTo(data);
				cv::cvtColor(orig, rgb, cv::COLOR_RGBA2BGR);
				cv::flip(rgb, flipped, 0);
				writer->write(flipped);
				t += 1.0 / 24;
				frame++;
				if (frame % 24 == 0)
					cout << frame / 24 << " seconds written" << endl;
			}
		
			cout << "Writing done" << endl;
			delete[] data;
		} else
		{
			Mixer::init();
			Mixer::begin(Mixer::defaultOutput());
			for (InputAudio* audio : audios)
			{
				Mixer::addSound(*audio->wav->instantiate());
			}

			if (fullscreen)
				Context::showFullscreen(&Context::getPrimaryMonitor());
			else
				Context::show();

			while (!Context::windowShouldClose())
			{
				double t = Mixer::getTime();

				setUniforms(shader, t, audios, videos);

				Context::fill();
				Context::defaultTarget().copyTo(lastFrame);
				Context::update();
				Context::doEvents();

				while (Mixer::getTime() == t);
				double t2 = Mixer::getTime();
				double lat = Mixer::getLatency();
				char title[50];
				sprintf(title, "time=%lf\tfps=%lf\tlat=%lf", t, 1.0 / (t2 - t), lat);
				Context::setTitle(utf8s2ws(title));
			}

			Mixer::finalize();
		}

		for (InputImage* i : images)
		{
			i->tex->finalize();
			delete i->tex;
			delete i;
		}
		for (InputVideo* i : videos)
		{
			i->vid->release();
			i->tex->finalize();
			delete i->vid;
			delete i->tex;
			delete i;
		}
		for (InputAudio* i : audios)
		{
			delete i->wav;
			delete i;
		}
		if (write)
			delete writer;
		vert.finalize();
		frag.finalize();
		shader.finalize();
		anip::finalize();
	}
	catch (exception& e)
	{
		cerr << e.what();
	}

	return 0;
}